/**
 * @brief 进度调度实验--时间片轮转
 * @copyright <<linux是怎样工作的>>
 */
// 添加以下定义以确保 CLOCK_MONOTONIC 可用（放在所有 include 前面或适当位置）
#define _POSIX_C_SOURCE 199309L

#include <err.h>
// #include <linux/time.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>

#define NLOOP_FOR_ESTIMATION 1000000000UL
#define NSECS_PER_MSEC       (unsigned long)(1e6)  // 每毫秒转化为ns
#define NSECS_PER_SEC        (unsigned long)(1e9)  // 每秒转化为ns

static unsigned long   nloop_per_resol; // 每轮空转次数
static struct timespec start;  // 进程启动时间

/**
 * @brief 获取时间间隔(ns)
 */
static inline long diff_nsec(struct timespec before, struct timespec after) {
    return ((after.tv_sec * NSECS_PER_SEC + after.tv_nsec) - (before.tv_sec * NSECS_PER_SEC + before.tv_nsec));
}

/**
 * @brief 估算每ms对应的空循环数
 * @return 返回执行一次NOOP的平均耗时(ns)
 */
static unsigned long estimate_loops_per_msec() {
    struct timespec before, after;
    // 获取系统时间值
    // CLOCK_MONOTONIC: 单调递增时钟，不受系统时间跳变的影响
    clock_gettime(CLOCK_MONOTONIC, &before);

    // 循环执行NOOP语句
    unsigned long i;
    for (i = 0; i < NLOOP_FOR_ESTIMATION; i++)
        ;

    clock_gettime(CLOCK_MONOTONIC, &after);

    // 计算每次NOOP的平均耗时
    return NLOOP_FOR_ESTIMATION * NSECS_PER_MSEC / diff_nsec(before, after);
}

/**
 * @brief 进程空转
 */
static inline void load(void) {
    unsigned long i;
    for (i = 0; i < nloop_per_resol; i++)
        ;
}

/**
 * @brief 子进程执行函数
 * 
 * @param id 进程编号
 * @param buf 
 * @param nrecord 循环次数
 */
static void child_fn(int id, struct timespec *buf, int nrecord) {
    int i;
    for (i = 0; i < nrecord; i++) {
        struct timespec ts;

        load();
        clock_gettime(CLOCK_MONOTONIC, &ts);
        buf[i] = ts;  // 记录本次结束时间
    }
    for (i = 0; i < nrecord; i++) {
        printf("%d\t%ld\t%d\n", id, diff_nsec(start, buf[i]) / NSECS_PER_MSEC, (i + 1) * 100 / nrecord);
    }
    exit(EXIT_SUCCESS);
}

static pid_t *pids;

/** 
 * @param nproc: 同时运行进程数量
 * @param total: 程序运行的总时长(ms)
 * @param resol: 采集统计信息的间隔(ms)
 */
int main(int argc, char *argv[]) {
    int ret = EXIT_FAILURE;

    if (argc < 4) {
        fprintf(stderr, "usage: %s <nproc> <total[ms]> <resolution[ms]>\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    int nproc = atoi(argv[1]);
    int total = atoi(argv[2]);
    int resol = atoi(argv[3]);

    if (nproc < 1) {
        fprintf(stderr, "<nproc>(%d) should be >= 1\n", nproc);
        exit(EXIT_FAILURE);
    }

    if (total < 1) {
        fprintf(stderr, "<total>(%d) should be >= 1\n", total);
        exit(EXIT_FAILURE);
    }

    if (resol < 1) {
        fprintf(stderr, "<resol>(%d) should be >= 1\n", resol);
        exit(EXIT_FAILURE);
    }

    if (total % resol) {
        fprintf(stderr, "<total>(%d) should be multiple of <resolution>(%d)\n", total, resol);
        exit(EXIT_FAILURE);
    }
    int nrecord = total / resol;

    struct timespec *logbuf = malloc(nrecord * sizeof(struct timespec));
    if (!logbuf) err(EXIT_FAILURE, "failed to allocate log buffer");

    puts("estimating the workload which takes just one milli-second...\n");
    nloop_per_resol = estimate_loops_per_msec() * resol;
    puts("end estimation\n");
    fflush(stdout);

    pids = malloc(nproc * sizeof(pid_t));
    if (pids == NULL) err(EXIT_FAILURE, "failed to allocate pid table");

    clock_gettime(CLOCK_MONOTONIC, &start);

    ret = EXIT_SUCCESS;
    int i, ncreated;
    for (i = 0, ncreated = 0; i < nproc; i++, ncreated++) {
        pids[i] = fork();
        if (pids[i] < 0) {
            int j;
            for (j = 0; j < ncreated; j++)
                kill(pids[j], SIGKILL);
            ret = EXIT_FAILURE;
            break;
        } else if (pids[i] == 0) {
            // 子进程
            child_fn(i, logbuf, nrecord);
            /* 不应该运行到这里 */
            abort();
        }
    }
    // 父进程
    for (i = 0; i < ncreated; i++)
        if (wait(NULL) < 0) warn("wait() failed.");

    free(pids);

    exit(ret);
}
